package com.huiquan.foundation.job;

import com.huiquan.analysis.constants.AnalysisSentenceConstant;
import com.huiquan.analysis.domain.User;
import com.huiquan.analysis.job.AnalysisInit;
import com.huiquan.analysis.job.AnalysisSortJob;
import com.huiquan.analysis.job.CheckDataJob;
import com.huiquan.analysis.service.AnalysisCheckExportFileService;
import com.huiquan.analysis.service.AnalysisDictionaryService;
import com.huiquan.analysis.service.AnalysisSentenceService;
import com.huiquan.answer.service.AnswerService;
import com.huiquan.dyad.service.DyadInitService;
import com.huiquan.foundation.constant.BusinessContants;
import com.huiquan.foundation.service.SubpackageService;
import com.huiquan.foundation.util.BusinessUtil;
import com.huiquan.framework.dao.EmailDao;
import com.huiquan.framework.utils.EMailUtil;
import com.huiquan.icd.dao.IcdCodeDao;
import com.huiquan.icd.job.IcdCodeJob;
import com.huiquan.icd.job.IcdDiseaseJob;
import com.huiquan.icd.service.IcdDiseaseExternalService;
import com.huiquan.icd.service.IcdDiseaseService;
import com.huiquan.icd.service.IcdDiseaseSymptomService;
import com.huiquan.management.constant.MigrateCode;
import com.huiquan.management.util.MigrateSwitch;
import com.huiquan.mark_sentence.service.MarkSentenceService;
import com.huiquan.symptom.job.SymptomJob;
import com.huiquan.vocab.service.*;
import com.huiquan.vocab.utils.SymptomStdDyadUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 定时任务管理器，将需要在定时执行的任务都在此处管理
 *
 * @author rocky
 */
@Component
public class JobManager {

    private static final Logger LOG = LoggerFactory.getLogger(IcdCodeJob.class);

    @Autowired
    private EMailUtil eMailUtil;
    @Autowired
    private IcdCodeDao icdCodeDao;
    @Autowired
    private EmailDao emailDao;
    @Autowired
    private AnalysisInit analysisInitJob;
    @Autowired
    private AnalysisSortJob analysisSortJob;
    @Autowired
    private IcdDiseaseJob icdDiseaseJob;
    @Autowired
    private SymptomJob symptomJob;
    @Autowired
    private AnalysisSentenceService analysisSentenceService;
    @Autowired
    private AnalysisDictionaryService analysisDictionaryService;
    @Autowired
    private CheckDataJob checkDataJob;
    @Autowired
    private DyadInitService dyadInitService;
    @Autowired
    private IcdDiseaseSymptomService icdDiseaseSymptomService;
    @Autowired
    private IcdDiseaseService icdDiseaseService;
    @Autowired
    private MigrateSwitch migrateSwitch;
    @Autowired
    private VocabElementTService vocabElementTService;
    @Autowired
    private IcdDiseaseExternalService icdDiseaseExternalService;
    @Autowired
    private AnalysisCheckExportFileService analysisCheckExportFileService;
    @Autowired
    private SymptomStdDyadUtil symptomStdDyadUtil;
    @Autowired
    private VocabElementReasonClassService vocabElementReasonClassService;
    @Autowired
    private SubpackageService subpackageService;
    @Autowired
    private VocabElementSynonymOOaService vocabElementSynonymOOaService;
    @Autowired
    private AnswerService answerService;
    @Autowired
    private VocabElementService vocabElementService;
    @Autowired
    private MarkSentenceService markSentenceService;

    @Value("${icd.code.file.script1}")
    private String icdCodeFileScript1;
    @Value("${icd.code.file.script2}")
    private String icdCodeFileScript2;
    @Value("${icd.code.file.script3}")
    private String icdCodeFileScript3;
    @Value("${icd.code.file.script4}")
    private String icdCodeFileScript4;

    // 判断是否当天的跑批需要启动
    private static boolean partternBashFlag = false;

    /**
     * 判断跑批是否要进行下去
     */
    @Scheduled(cron = "0 5 0 * * ? ")
    public void checkPartternBash() {
        // 判断跑批是否进行
        // 构建系统管理员
        User user = new User();
        user.setUserId(1l);
        user.setRealName("系统管理员");
        int num = icdCodeDao.checkActionRecord();
        // 主动复位
        partternBashFlag = false;
        if (num > 0) {
            partternBashFlag = false;
            LOG.info("已经有跑批在进行中");
        } else {
            icdCodeDao.addCompletedActionRecord(2, user);
            partternBashFlag = true;
            LOG.info("跑批记录开始跑批");
        }
    }

    /**
     * 初始化系统中的数据，每天凌晨开始执行
     */
    @Scheduled(cron = "0 10 0 * * ? ")
    public void initData() {
        LOG.info("generateIcdCode begin!");

        if (BusinessUtil.isProductionEnvironment() && partternBashFlag) { // 在线上环境才执行作业
            try {
                // 构建系统管理员
                User user = new User();
                user.setUserId(1l);
                user.setRealName("系统管理员");

                // 调用第一个脚本：标注分词的模型处理
                LOG.info("icdCodeFileScript1 begin!");
                BusinessUtil.excuteShell(new String[]{icdCodeFileScript1});
                LOG.info("icdCodeFileScript1 end!");

                // 跑数据相关的过程几乎用不到本机资源且与其他，用另外一个线程来执行
                Thread t = new Thread(new InitRemoteDataRunnable(user));
                t.start();

                // 调用模型将未标注的进行分词标注，半标注的进行BME标注（需要模型处理）
                //analysisInitJob.initZs();

                // 将主诉未标注句子排序（需要预标注）
                //analysisSortJob.sortZs();

                // 预标注医学影像数据
                // analysisInitJob.initCt();

                // 获取现病史 器械检查（医学影像） 数据
                //analysisInitJob.initXbsDataWithSecondType(AnalysisSentenceConstant.XBS_SOURCE_TYPE_IMAGEOLOGY_INFO);

                // 预标注现病史需要bme处理的数据
//                analysisInitJob.initXbs();

                // 预标注结束后初始化分包数据
                //subpackageService.doInitSubpackage(GradeContants.TYPE_ANALYSIS_XBS_IMAGEOLOGY,null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            LOG.info("非线上环境");
        }
    }

    /**
     * 每天晚上22点导出已标记分类的语句到181
     */
    @Scheduled(cron = "0 0 22 * * ? ")
    public void exportMarkedSentence() {
        markSentenceService.exportMarkedSentence();
    }

    /**
     * 每天早上6点导入未标记分类的语句到bas_mark_sentence表
     */
    @Scheduled(cron = "0 0 6 * * ? ")
    public void importUnmarkSentence() {
        try {
            markSentenceService.importUnmarkSentence();

        } catch (Exception e) {
            handleException(e, "每天早上6点导入未标记分类的语句到bas_mark_sentence表出错");
        }
    }

    @Value("${vocab.init.script}")
    private String vocabInit;
    @Value("${disease.symptom.o.init.script}")
    private String diseaseSymptomOInit;
    @Value("${disease.symptom.black.script}")
    private String diseaseSymptomBlackInit;
    @Value("${disease.sex.age.percent.init.script}")
    private String diseaseAgeInit;


    /**
     * 每天早上5点更新症状和疾病黑白名单
     *
     * @throws Exception
     */
    @Scheduled(cron = "0 0 5 * * ? ")
    public void initDataByPatternBatch() {
        if (BusinessUtil.isProductionEnvironment() && partternBashFlag) { // 在线上环境才执行作业

            // 初始化元素原因词分类性别年龄校验
            try {

                LOG.info("初始化元素原因词分类性别年龄校验开始！");
                vocabElementReasonClassService.initSexAge();
                LOG.info("初始化元素原因词分类性别年龄校验结束！");

            } catch (Exception e) {
                handleException(e, "初始化元素原因词分类性别年龄校验出错");
            }

            // 初始化疾病和症状黑白名单
            try {

                LOG.info("初始化疾病和症状黑白名单开始！");
                BusinessUtil.excuteShell(new String[]{diseaseSymptomOInit});

                LOG.info("初始化疾病症状的o的分页");
                icdDiseaseSymptomService.initOrderOSymptom();

                LOG.info("初始化疾病和症状黑白名单结束！");

            } catch (Exception e) {
                handleException(e, "初始化疾病和症状黑白名单出错");
            }

            // 初始化疾病性别年龄
            try {

                LOG.info("初始化疾病性别年龄开始！");
                BusinessUtil.excuteShell(new String[]{diseaseAgeInit});

                LOG.info("初始化疾病性别年龄");
                icdDiseaseService.initSexAge();

                LOG.info("初始化疾病性别年龄结束！");

            } catch (Exception e) {
                handleException(e, "初始化疾病性别年龄出错");
            }

            // 初始化症状和小词
            try {

                LOG.info("初始化症状和小词开始！");
                BusinessUtil.excuteShell(new String[]{vocabInit});
                LOG.info("初始化症状和小词结束！");

            } catch (Exception e) {
                handleException(e, "初始化症状和小词出错");
            }

            // 初始化元素t
            try {

                LOG.info("初始化元素t开始！");

                // 初始化量化
                vocabElementTService.elementTimeQuantizationInit();

                LOG.info("初始化元素t结束！");

            } catch (Exception e) {
                handleException(e, "初始化元素t出错");
            }

            // 校验analysis 导出文件的大小变化
            try {
                LOG.info("校验analysis导出文件大小变化开始");
                analysisCheckExportFileService.checkAnalysisFile();
                LOG.info("校验analysis导出文件大小变化结束");
            } catch (Exception e) {
                handleException(e, "校验analysis导出文件大小变化出错");
            }
            // 初始化滕州疾病
            try {
                LOG.info("初始化滕州疾病开始");
                icdDiseaseExternalService.initExternalData();
                LOG.info("初始化滕州疾病结束");
            } catch (Exception e) {
                handleException(e, "校验初始化滕州疾病出错");
            }

            try {
                LOG.info("初始化数据-疾病科室黑名单开始");
                BusinessUtil.excuteShell(new String[]{diSectionBlackWhiteScript});
                LOG.info("初始化数据-疾病科室黑名单结束");
            } catch (Exception e) {
                handleException(e, "初始化数据-疾病科室黑名单");
            }

            try {
                LOG.info("初始化数据-科室是否参与计算开始");
                BusinessUtil.excuteShell(new String[]{sectionDnnDisableScript});
                LOG.info("初始化数据-科室是否参与计算结束");
            } catch (Exception e) {
                handleException(e, "初始化数据-科室是否参与计算");
            }

            try {
                LOG.info("初始化数据-疾病科室知识库开始");
                icdDiseaseJob.generateDiseaseSectionRepository();
                LOG.info("初始化数据-疾病科室知识库结束");
            } catch (Exception e) {
                handleException(e, "初始化数据-疾病科室知识库");
            }

            // 从数据服务器同步疾病指标黑白名单到数据库
            try {
                LOG.info("初始化数据-疾病指标黑白名单开始");
                icdDiseaseJob.initDiseaseInspection();
                LOG.info("初始化数据-疾病指标黑白名单结束");

            } catch (Exception e) {
                handleException(e, "Icd disease job update desease inspection");
            }

            // 在疾病维表更新之后要获取疾病指标的概率数据
            long codeUpdateRecordId = icdCodeDao.getCodeUpdateRecordIdBeforeDays(2);
            if (codeUpdateRecordId > 0) {
                try {
                    LOG.info("初始化数据-疾病指标已校验概率开始");
                    icdDiseaseJob.initDiseaseInspectionChecked();
                    LOG.info("初始化数据-疾病指标已校验概率结束");
                } catch (Exception e) {
                    handleException(e, "Icd disease job update desease inspection");
                }
            }

            // 初始化疾病元素黑名单二次校验数据
            try {
                LOG.info("初始化疾病元素黑名单二次校验数据开始！");
                BusinessUtil.excuteShell(new String[]{diseaseSymptomBlackInit});
                LOG.info("初始化疾病元素黑名单二次校验数据结束！");

            } catch (Exception e) {
                handleException(e, "初始化疾病元素黑名单二次校验数据出错");
            }
            // 元素性别年龄同义词处理
            try {
                LOG.info("初始化数据-性别年龄同义词处理开始");
                vocabElementService.initSexAgeSynonymy();
                LOG.info("初始化数据-性别年龄同义词处理结束");

            } catch (Exception e) {
                handleException(e, "初始化数据-性别年龄同义词处理出错");
            }
        }

    }

    /**
     * 每天早上5点半更新二元组
     *
     */
    @Scheduled(cron = "0 30 5 * * ? ")
    public void initDyad() {
        if (BusinessUtil.isProductionEnvironment() && partternBashFlag) { // 在线上环境才执行作业
            // TODO 之后用事务处理来优化
            // 二元组初始化
            LOG.info("初始化二元组开始！");
//            vocabDyadInitService.init();
            dyadInitService.init();
            LOG.info("初始化二元组结束！");
        }

    }

    /**
     * 每天早上7点更新搜索引擎索引和症状显示词
     *
     */
    @Scheduled(cron = "0 0 7 * * ? ")
    public void updateSphinxIndexAndInitSymptomData() {
        if (BusinessUtil.isProductionEnvironment() && partternBashFlag) { // 在线上环境才执行作业

            // TODO 之后用事务处理来优化
            // 更新搜索引擎索引和redis
            try {
                updateSphinxIndex();
            } catch (Exception e) {
                handleException(e, "更新搜索引擎索引出错");
            }

            // 导入症状显示词
            try {
                symptomJob.importSymptomRevealSynonym();
            } catch (Exception e) {
                handleException(e, "症状显示词同义词导入出错");
            }

            LOG.info("更新搜索引擎索引和症状显示词结束");
        }
    }

    public void updateSphinxIndex() {
        if (BusinessUtil.isProductionEnvironment() && partternBashFlag) { // 在线上环境才执行作业

            // 更新搜索引擎索引
            try {
                analysisSentenceService.updateSphinxIndex("--all");
            } catch (Exception e) {
                handleException(e, "更新搜索引擎索引出错");
            }

            // 更新redis里的数据
            try {
                analysisSentenceService.updateRedisDataByDB();
            } catch (Exception e) {
                handleException(e, "更新redis数据出错");
            }
        }
    }

    public void updateSphinxIndexForTest() {

        // 更新搜索引擎索引
        try {
            LOG.info("开始执行更新搜索引擎索引");
            analysisSentenceService.updateSphinxIndex("--all");
            LOG.info("执行更新搜索引擎索引结束");
        } catch (Exception e) {
            handleException(e, "更新搜索引擎索引出错");
        }

        // 更新redis里的数据
        try {
            LOG.info("开始执行更新redis");
            analysisSentenceService.updateRedisDataByDB();
            LOG.info("执行更新搜索redis结束");
        } catch (Exception e) {
            handleException(e, "更新redis数据出错");
        }
    }

    /**
     * 数据校验
     *
     * @throws Exception
     */
    @Scheduled(cron = "00 10 12 * * ? ")
    public void checkData() throws Exception {
        LOG.info("check Data  sql begin");
        String rtn = "";
        try {
            // 纯SQL校验数据
            checkDataJob.checkData("sql");
        } catch (Exception e) {
            handleException(e, e.getMessage());
        }
        LOG.info("check Data  sql end");
//        LOG.info("check Data  class begin");
//        try {
//            // 用java代码的方式校验
//            checkDataJob.checkData("java");
//        } catch (Exception e) {
//            if (e instanceof UndeclaredThrowableException) {
//                UndeclaredThrowableException ex = (UndeclaredThrowableException) e;
//                Throwable a = ex.getUndeclaredThrowable();
//                LOG.info(a.getMessage());
//                handleException(e, a.getMessage());
//            } else {
//                handleException(e, e.getMessage() == null ? "数据校验存在错误数据或者失败" : e.getMessage());
//            }
//        }
//        LOG.info("check Data  class end");
    }

    /**
     * 同步数据，在跑数据之前搞定
     */
    @Value("${di.section.black.white.script}")
    private String diSectionBlackWhiteScript;
    @Value("${section.dnn.disable.script}")
    private String sectionDnnDisableScript;

    @Scheduled(cron = "0 0 20 * * ? ")
    public void synchronizeData() {

        if (BusinessUtil.isProductionEnvironment()) { // 在线上环境才执行作业

            // 初始化一些公用的数据，后续二元组初始化等功能中有用到
            try {
                preInit();
            } catch (Exception e) {
                handleException(e, "初始化公用数据");
            }

            // 从症状大词同步数据到症状黑名单待校验
            try {
                symptomJob.updateMisSymptom();
            } catch (Exception e) {
                handleException(e, "Symptom job update mis symptom");
            }

            // 症状标准词生成需要预先将二元组中标准分词更新
            try {
                symptomStdDyadUtil.initDyadStd();
            } catch (Exception e) {
                handleException(e, "Dyad std array init");
            }

            // 初始化症状展现词症状对应标准词
            try {
                symptomJob.initRevealStdCurrent();
            } catch (Exception e) {
                handleException(e, "Symptom init reveal element std");
            }

            // 同步词典数据，在同步数据之前搞定
            try {
                analysisDictionaryService.synchronizationDictionary();
            } catch (Exception e) {
                handleException(e, "同步字典数据");
            }

            // 更新外部疾病数据
            try {
                icdDiseaseExternalService.initExternalData();
            } catch (Exception e) {
                handleException(e, "更新外部疾病数据出错");
            }

            // 同步到数据服务器
            try {
                migrate();
                // 调用 crf模型训练脚本
                LOG.info("crf模型训练脚本 begin!");
                BusinessUtil.excuteShell(new String[]{icdCodeFileScript4});
                LOG.info("crf模型训练脚本 end!");
            } catch (Exception e) {
                handleException(e, "Migrate");
            }

        }
    }

    /**
     * 每周一凌晨更新问答服务结果数据
     */
    @Scheduled(cron = "0 0 1 * * 1 ")
    public void updateAnswerData() throws IOException {
        answerService.updateAnswerData();
    }

    private void handleException(Exception e, String string) {
        LOG.error("Migrate error:" + string);

        // 报错后发邮件通知
        List<String> address = emailDao.retrieveAddressList(BusinessContants.SEND_EMAIL_GROUP_DATA_RUNNING_ERROR);
        String subject = "标注系统同步数据出错：" + string;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        e.printStackTrace(new PrintStream(baos));
        String content = baos.toString();

        if (!address.isEmpty()) {
            try {
                eMailUtil.sendBatchEmail(content, subject, address);
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }

        e.printStackTrace();
    }

    class InitRemoteDataRunnable implements Runnable {

        private User user;

        public InitRemoteDataRunnable(User user) {
            this.user = user;
        }

        @Override
        public void run() {
            try {
                // 查询当天是否需要疾病更新
                long codeUpdateRecordId = icdCodeDao.getCodeUpdateRecordIdBeforeDays(2);
                if (codeUpdateRecordId > 0) {
                    runDisease();
                }

                // 调用第三个脚本：整体数据运算
                LOG.info("icdCodeFileScript3 begin!");
                BusinessUtil.excuteShell(new String[]{icdCodeFileScript3});
                LOG.info("icdCodeFileScript3 end!");

                // 维护动作记录
                if (codeUpdateRecordId > 0) {
                    icdCodeDao.updateActionRecord(codeUpdateRecordId, user);
                }
                LOG.info("181 running end!");
            } catch (Exception e) {
                LOG.error("InitRemoteDataRunnable error!");
                e.printStackTrace();
            }
        }
    }

    public void runDisease() throws Exception {
        // 将数据输出到文件
        migrateSwitch.migrateByCode(MigrateCode.DISEASE_HOSPITAL);

        // 调用第二个脚本，疾病更新时专用
        LOG.info("icdCodeFileScript2 begin!");

        BusinessUtil.excuteShell(new String[]{icdCodeFileScript2});
        LOG.info("icdCodeFileScript2 end!");
    }

    public void migrate() {
        LOG.info("Migrate table begin");

        for (MigrateCode mc : MigrateCode.values()) {
            if (mc.getDailyFlag()) {
                try {
                    migrateSwitch.migrateByCode(mc);
                } catch (Exception e) {
                    handleException(e, mc.name());
                }
            }
        }

        LOG.info("Migrate table end");
    }

    /**
     * 在所有初始化之前需要做的事情
     */
    public void preInit() {
        // o和oa同义词初始化
        try {
            vocabElementSynonymOOaService.init();
        } catch (Exception e) {
            handleException(e, "初始化o和oa同义词");
        }

        // 已校验二元组的元素标准词维护
        try {
            symptomStdDyadUtil.initDyadStd();
        } catch (Exception e) {
            handleException(e, "Dyad std array init");
        }
    }
}
